package com.myutils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.log4j.Logger;

/**
 * Ftp操作工具类：上传、下载、删除
 * 
 * @author Lan
 * @date 2012-6-14
 */
public class FTPUtil {

	private final static Logger logger = Logger.getLogger(FTPUtil.class);

	private FTPClient ftpClient = null;

	//重新执行的次数
	private int count_download = 0;
	private int count_update = 0;
	private int count_getSize = 0;
	private int count_getExist = 0;
	
	private final int BUFFER_SIZE = 1024 * 10;
	
	//重新执行的最多次数
	private final int MAX_REPEAT_COUNT = 3;
	
	public FTPUtil(String host, int port, String username, String password) throws IOException {
		try {
			if(!this.connect(host, port, username, password)){
				throw new IOException("实例化FtpUtil对象错误");
			}
		} catch (IOException e) {
			throw e;
		}
	}

	private boolean download(String remoteFile, File localFile) throws IOException{
		boolean downloadSucc = false;
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(localFile);
    		downloadSucc = ftpClient.retrieveFile(remoteFile, fos);
    		if(!downloadSucc){
    			logger.debug("FTP下载文件失败[" + remoteFile + "]");
    			if(count_download < MAX_REPEAT_COUNT){
    				count_download++;
    				logger.debug("第" + count_download + "次重新下载[" + remoteFile + "]");
    				IOUtils.closeQuietly(fos); 
    				download(remoteFile, localFile);
    			}
    		}
		} catch (IOException e) {
			logger.debug("下载时出现异常[" + remoteFile + "]", e);
			if(e instanceof ConnectException){
				if(count_download < MAX_REPEAT_COUNT){
					count_download++;
					logger.debug("第" + count_download + "次重新下载[" + remoteFile + "]");
					IOUtils.closeQuietly(fos); 
					download(remoteFile, localFile);
				}else{
					throw e;
				}
			}else{
				throw e;
			}
		} finally {
			IOUtils.closeQuietly(fos); 
		}
		return downloadSucc;
	}
	/**
	 * ftp批量下载
	 * @author Lan
	 * @param localDir 下载的文件存放在本地的目录
	 * @param remoteFileList<FTP上相对FTP根路径的文件全路径> 远程服务器上需要下载的文件:hdweb_qj/a/b/c.html
	 * @return Map<String, Integer> 文件下载状态<文件路径,状态值>  状态值：0失败; 1成功; 2文件不存在
	 */
	public Map<String, Integer> downloadBatch(String localDir, Collection<String> remoteFileList) throws IOException {
        InputStream in = null;
        
        Map<String, Integer> downStatusMap = new LinkedHashMap<String, Integer>();
        
        try { 
        	ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
        	ftpClient.setBufferSize(BUFFER_SIZE);
			ftpClient.enterLocalPassiveMode();
			
            for(String remoteFile : remoteFileList){
            	this.count_download = 0;
            	this.count_getExist = 0;
            	this.count_getSize = 0;
            	
            	String savePath = FileUtil.formatPath(localDir + File.separator + remoteFile);
            	FileUtil.createDir(savePath);
            	
            	File localFile = new File(savePath);
            	
            	boolean downloadSucc = download(remoteFile, localFile);
            	
            	if(!downloadSucc){
            		if(!isFtpFileExist(remoteFile)){
            			logger.error("【下载失败，文件不存在】：" + remoteFile);
            			downStatusMap.put(remoteFile, 2);//文件不存在
            		}else{
            			downStatusMap.put(remoteFile, 0);//下载失败
            			logger.error("【下载失败，未知错误】：" + remoteFile);
            		}
            	}else{
            		long ftpFileSize = this.getFtpFileSize(remoteFile);
            		long localFileSize = localFile.length();
            		if(ftpFileSize == localFileSize){
            			downStatusMap.put(remoteFile, 1);//下载成功
            		}else{
            			downStatusMap.put(remoteFile, 0);//下载失败
            			logger.error(remoteFile + "文件下载不完整. 实际大小=" + ftpFileSize + " byte,只下载了" + localFileSize + "byte");
            		}
            	}
            }
            
        } catch (IOException e) {
			logger.error("【下载文件出错】", e);
		} finally { 
			IOUtils.closeQuietly(in);
		}
		
		return downStatusMap;
       
    } 
	
	/**
	 * 上传单独的文件
	 * @author Lan
	 * @param localFile
	 * @param localDir
	 * @return
	 */
	public boolean uploadSingleFile(String localFile, String localDir){
		Set<String> set = new HashSet<String>();
		set.add(localFile);
		Set<String> failUpSet = this.uploadBatch(set, localDir);
		if(failUpSet.size() == 0)
			return true;
		else
			return false;
	}
	/**
	 * 批量上传
	 * @author Lan
	 * @param localFileList 相对于localDir的文件路径集合(dir1/dir2/file.txt)
	 * @param localDir 本地存放文件的目录(/usr/local/)
	 * @return Set<String> 上传失败的文件集合
	 */
	public Set<String> uploadBatch(Collection<String> localFileList, String localDir){
		logger.debug("开始上传发布");
		Set<String> failSet = new LinkedHashSet<String>();
		
		try {
			ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
			ftpClient.enterLocalPassiveMode();
			ftpClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
			
			for(String path : localFileList){
				String localFilePath = FileUtil.formatPath(localDir + path);
				
				File localFile = new File(localFilePath);
				
				if(!this.uploadFile(localFile, path)){
					failSet.add(path);
					logger.error("【上传失败：" + path + "】");
				}
			}
		} catch (IOException e) { 
			logger.error("【上传文件失败】", e);
        }
        return failSet;
	}
	
	/**
	 * 上传文件的操作
	 * @author Lan
	 * @param localFile 本地文件
	 * @param remoteFilePath 上传至FTP的文件全路径（即相对于FTP根目录的文件全路径：dir1/dir2/file.txt）
	 * @return
	 * @throws IOException
	 */
	private Boolean uploadFile(File localFile, String remoteFilePath) throws IOException{
		Boolean isSucc = false;
		InputStream in = null;
		try {
			String directory = FileUtil.getParentPath(remoteFilePath);
			
			if(this.enterIntoFtpDir(directory)){
				
				ftpClient.setBufferSize(BUFFER_SIZE); 
				
				String fileName = FileUtil.getName(remoteFilePath);
				in = new FileInputStream(localFile);
				
				isSucc = ftpClient.storeFile(fileName, in); 
				
				if(!isSucc){
					logger.debug("FTP上传文件失败[" + remoteFilePath + "]");
					if(count_update < MAX_REPEAT_COUNT){
						count_update++;
						logger.debug("第" + count_update + "次重新上传[" + remoteFilePath + "]");
						IOUtils.closeQuietly(in);
						uploadFile(localFile, remoteFilePath);
					}else{
						logger.error("【重新上传" + count_update + "次后仍然失败，停止上传】");
					}
				}
				
				int reply = ftpClient.getReplyCode();
				
				if (reply == FTPReply.CLOSING_DATA_CONNECTION) {//226
					logger.debug("文件[" + remoteFilePath + "]上传成功");
				}
			} 
		} catch (IOException e) {
//			logger.debug("上传时出现异常[" + remoteFilePath + "]", e);
			if(e instanceof ConnectException){
				if(count_update < MAX_REPEAT_COUNT){
					count_update++;
					logger.debug("第" + count_update + "次重新上传[" + remoteFilePath + "]");
					IOUtils.closeQuietly(in);
					uploadFile(localFile, remoteFilePath);
				}else{
					throw e;
				}
			}else{
				throw e;
			}
		} finally {
			IOUtils.closeQuietly(in);
		}
		return isSucc;
	}
	
	/**
	 * 批量删除文件
	 * @param remoteFileList 待删除的远程文件集合
	 * @return 删除失败的文件集合
	 * @author Lan
	 */
	public Set<String> delFileBatch(Collection<String> remoteFileList){
		Set<String> delFailSet = new LinkedHashSet<String>();
		try {
			for(String path : remoteFileList){
				ftpClient.changeWorkingDirectory("/");
				String dir = FileUtil.getParentPath(path);
				String delName = FileUtil.getName(path);
				
				ftpClient.changeWorkingDirectory(dir);
				ftpClient.listNames("");
				
				if(!ftpClient.deleteFile(delName)){
					logger.error("删除文件失败" + path);
					delFailSet.add(path);
				} else {
					
				}
			}
		} catch (IOException e) {
			logger.error("删除文件异常", e);
		}
		return delFailSet;
	}
	
	/**
	 * 进入FTP目录，若目录不存在，则依次创建并进入
	 * @param dirPath FTP目录，如hdweb_qj/a/b/c
	 * @return 创建目录成功
	 */
	public Boolean enterIntoFtpDir(String dirPath){
		Boolean mkdirSucc = true;
		try {
			if(!StringUtils.endsWith(dirPath, "/"))
				dirPath = dirPath + "/";
			ftpClient.changeWorkingDirectory("/");
			
			//如果目录不是"/"根目录且目录进入失败，即不存在
			if(!StringUtils.equals(dirPath, "/") && !ftpClient.changeWorkingDirectory(Function.gbkToiso8859(dirPath))){
				int start = 0;//该层目录名的开始
				int end = 0;//该层目录名的结束
				if(dirPath.startsWith("/")){   
	                start = 1;   
	            }
				end = dirPath.indexOf("/", start);
				//循环遍历，（创建）进入子目录
				while(end > start){
					String subDir = Function.gbkToiso8859(dirPath.substring(start, end));
					if(!ftpClient.changeWorkingDirectory(subDir)){//远程目录进入失败，不存在该目录
						if(ftpClient.makeDirectory(subDir)){//创建该子目录
							ftpClient.changeWorkingDirectory(subDir);//进入该子目录
						}else{
							logger.error("创建目录失败:" + subDir);
							mkdirSucc = false;
							break;
						}
					}else{
						logger.debug("进入远程目录-> " + subDir);
					}
					
					start = end + 1;
					end = dirPath.indexOf("/", start);
				}
			}
		} catch (IOException e) {
			logger.error("【FTP进入远程目录失败】", e);
			mkdirSucc = false;
		}
		//logger.debug("创建进入远程目录结果：" + mkdirSucc);
		return mkdirSucc;
	}
	
	/**
	 * 连接并登录FTP
	 * @param host
	 * @param port
	 * @param username
	 * @param password
	 * @return true-连接并登录成功, false-连接或登录不成功,则关闭连接并退出登录
	 * @throws IOException 
	 */
	private boolean connect(String host, int port, String username, String password) throws IOException {
		boolean isConnected = false;
		boolean isLogined = false;
		if (ftpClient == null) {
			ftpClient = new FTPClient();
			ftpClient.setControlEncoding("GBK");

			try {
				logger.debug("FTP连接登录: host=" + host + ",port=" + port + " " + username + "/" + password);
				ftpClient.connect(host, port);
				int reply = ftpClient.getReplyCode();
				if (reply == FTPReply.SERVICE_READY) {//220
					isConnected = true;
					logger.debug("FTP连接成功");
				}
				
				ftpClient.login(username, password);
				reply = ftpClient.getReplyCode();
				if (reply == FTPReply.USER_LOGGED_IN) {//230
					isLogined = true;
					logger.debug("FTP用户登录成功");
				}
				
				if(!FTPReply.isPositiveCompletion(reply)){
					logger.error("FTP连接失败，请检查用户名和密码是否正确");
					this.disconnect();
				}
			} catch (SocketException e) {
				logger.error("FTP连接失败，请检查地址是否正确", e);
				this.disconnect();
				throw e;
			} catch (IOException e) {
				logger.error("FTP连接失败，请检查端口是否正确", e);
				this.disconnect();
				throw e;
			}
		}
		return (isConnected && isLogined);
	}

	/**
	 * 退出登录并关闭FTP连接
	 * 
	 * @author Lan
	 */
	public void disconnect() {
		try {
			if (ftpClient != null) {
				logger.debug("FTP退出登录");
				ftpClient.logout();
			}
		} catch (IOException e) {
			logger.error("FTP退出登录发生异常", e);
		} finally {
			if (ftpClient != null && ftpClient.isConnected()) {
				try {
					logger.debug("FTP关闭连接");
					ftpClient.disconnect();
				} catch (IOException e) {
					logger.error("关闭FTP连接发生异常", e);
				}
			}
		}
	}
	
	/**
	 * 获取FTP服务器上指定目录下的文件
	 * @param ftpDir
	 * @return 文件路径集合
	 */
	public List<String> getRemoteFiles(String ftpDir) {
		List<String> list = new ArrayList<String>();
		try {
			String files[] = ftpClient.listNames(ftpDir);
			if (files == null || files.length == 0)
				logger.debug(ftpDir + " 没有任何文件!");
			else {
				for (String fileName : files) {
					logger.debug("ftp文件路径-->" + ftpDir + ":" + fileName);
					list.add(ftpDir + File.separator + fileName);
				}
			}
		} catch (IOException e) {
			logger.error("文件获取失败", e);
		}
		return list;
	}
	
	/**
	 * 获取FTP上文件的大小
	 * @author Lan
	 * @param ftpFileName FTP上的文件：hdweb_qj/a/b/c.html
	 * @return 文件的大小(字节)
	 */
	public long getFtpFileSize(String ftpFileName){
		long size = 0;
		FTPFile[] ftpFiles = null;
		try {
			//获取该文件
			ftpFiles = ftpClient.listFiles(ftpFileName);
		} catch (IOException e) {
			logger.debug("获取文件大小时，出现异常[" + ftpFileName + "]", e);
			if(e instanceof ConnectException){
				if(count_getSize < MAX_REPEAT_COUNT){
					count_getSize++;
					logger.debug("第" + count_getSize + "次重新获取文件大小");
					getFtpFileSize(ftpFileName);
				}
			}
		}
		
		if(ftpFiles != null && ftpFiles.length == 1){
//			logger.debug("ftpFile:" + ftpFileName + ",[size=" + ftpFiles[0].getSize() + "]");
			size = ftpFiles[0].getSize();
		}
		return size;
	}
	
	/**
	 * 判断FTP上的文件是否存在
	 * @author Lan
	 * @param ftpFileName FTP上的文件：hdweb_qj/a/b/c.html
	 * @return true-存在, false-不存在
	 */
	public boolean isFtpFileExist(String ftpFileName){
		boolean exist = false;
		FTPFile[] ftpFiles = null;
		try {
			//获取该文件
			ftpFiles = ftpClient.listFiles(ftpFileName);
		} catch (IOException e) {
			logger.debug("判断FTP上的文件是否存在时，出现异常[" + ftpFileName + "]", e);
			if(e instanceof ConnectException){
				if(count_getExist < MAX_REPEAT_COUNT){
					count_getExist++;
					logger.debug("第" + count_getExist + "次重新判断文件是否存在", e);
					isFtpFileExist(ftpFileName);
				}
			}
		}
		
		if(ftpFiles != null){
			if(ftpFiles.length == 1){
//				logger.debug("ftpFile:" + ftpFileName + ",[size=" + ftpFiles[0].getSize() + "]");
				exist = true;
			}else if(ftpFiles.length == 0){
				logger.error(ftpFileName + " 不存在");
			}
		}
		
		return exist;
	}
	
	
}
